﻿// -----------------------------------------------------------------------
// This class 
//
// Author: Dan Buvid
// Date: April 13, 2012
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BellaCode.Collections.ObjectModel;

namespace ResidencyPlacement
{
    public class ResidencySystem
    {
        private ObservableHashSet<Student> students;
        private ObservableHashSet<Hospital> hospitals;

        public ResidencySystem()
        {
            students = new ObservableHashSet<Student>();
            hospitals = new ObservableHashSet<Hospital>();
        }

        private bool stateInvariantCheck()
        {
            bool result = true;

            // The state invariant asserts that for all students in the
            // student set if their ids match they are the same student
            foreach (var s1 in students)
            {
                foreach (var s2 in students)
                {
                    if (s1.ID == s2.ID && s1 != s2)
                    {
                        result = false;
                    }
                }
            }

            // The state invariant asserts that for all hospitals in the
            // hospitals set if their ids match they are the same hospital
            foreach (var h1 in hospitals)
            {
                foreach (var h2 in hospitals)
                {
                    if (h1.ID == h2.ID && h1 != h2)
                    {
                        result = false;
                    }
                }
            }
            
            // The state invariant asserts that for all applications of
            // all the students in the student set the hospital id much
            // match an id of a hospital in the hospitals set.
            foreach (var s in students)
            {
                foreach (var a in s.Applications)
                {
                    if (!hospitals.Any(h => h.ID == a.Hospital))
                    {
                        result = false;
                    }
                }
            }
            
            // The state invariant asserts that for all hospitals in the
            // hospitals set the domain of the applied function should be
            // a subset of the students in the student set.
            foreach (var h in hospitals)
            {
                HashSet<Student> appliedDomain = new HashSet<Student>(h.Applied.Keys);
                if (!appliedDomain.IsSubsetOf(students))
                {
                    result = false;
                }
            }

            return result;
        }

        /// <summary>
        /// This method implements the 'AddStudent' operation.
        /// </summary>
        /// <param name="newStudent">The student to add.</param>
        public void AddStudent(Student newStudent)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "AddStudent", "before");

            if (students.Any(s => s.ID == newStudent.ID))
                throw new PreconditionException("System", "AddStudent", "Cannot add a student with an ID that already exists.");

            students.Add(newStudent);

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "AddStudent", "after");
        }

        /// <summary>
        /// This method implements the 'AddHospital' operation.
        /// </summary>
        /// <param name="newHospital">The hospital to be added.</param>
        public void AddHospital(Hospital newHospital)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "AddHospital", "before");

            if (hospitals.Any(h => h.ID == newHospital.ID))
                throw new PreconditionException("System", "AddHospital", "Cannot add a hospital with an ID that already exists.");

            if (!(newHospital.NumberOfPositions > 0))
                throw new PreconditionException("System", "AddHospital", "A new hospital must have at least one open position.");

            hospitals.Add(newHospital);

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "AddHospital", "after");
        }

        /// <summary>
        /// This method implements the 'StudentChangeEntranceExamMark' operation.
        /// </summary>
        /// <param name="student">The student to change the mark for.</param>
        /// <param name="newMark">The new mark for the student.</param>
        public void StudentChangeEntranceExamMark(Student student, int newMark)
        {
            student.ChangeEntranceExamMark(newMark);
        }

        /// <summary>
        /// This method implements the 'StudentAppliesToAHospital' operation.
        /// </summary>
        /// <param name="student">The student that applies.</param>
        /// <param name="hospital">The hospital to apply to.</param>
        /// <param name="application">The application.</param>
        public void StudentAppliesToAHospital(Student student, Hospital hospital, Application application)
        {
            student.ApplyToHospital(application);
            hospital.StudentApplies(student, application);
        }

        /// <summary>
        /// This method implements the 'StudentAttendsInterview' operation.
        /// </summary>
        /// <param name="student">The student that attends the interview.</param>
        /// <param name="hospital">The hospital that interviews the student.</param>
        /// <param name="interviewMark">The mark for the interview.</param>
        public void StudentAttendsInterview(Student student, Hospital hospital, int interviewMark)
        {
            student.AttendInterview(hospital.ID, interviewMark);
            hospital.StudentIsInterviewed(student, interviewMark);
        }

        /// <summary>
        /// This method implements the 'StudentIsSelectedAndOffered' operation.
        /// </summary>
        /// <param name="student">The student that is selected.</param>
        /// <param name="hospital">The hospital that offers the position.</param>
        public void StudentIsSelectedAndOffered(Student student, Hospital hospital)
        {
            student.GotOffer(hospital.ID);
            hospital.StudentIsSelected(student);
        }

        /// <summary>
        /// This method implements the 'StudentRepliesToOffer' operation.
        /// </summary>
        /// <param name="student">The student that replies.</param>
        /// <param name="hospital">The hospital the student replies to.</param>
        /// <param name="decision">The student's decision (true==yes, false===no)</param>
        public void StudentRepliesToOffer(Student student, Hospital hospital, bool decision)
        {
            student.DecideOnOffer(hospital.ID, decision);
            hospital.StudentRepliedBackToOffer(student, decision);
        }

        /// <summary>
        /// This method implements the 'NumberOfStudentsNotAcceptedAnywhere' operation.
        /// </summary>
        /// <returns>The number of students not accepted by any hospitals.</returns>
        public int NumberOfStudentsNotAcceptedAnywhere()
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfStudentsNotAcceptedAnywhere", "before");

            int count = 0;

            count = students.Count(s => s.Applications.
                All(a => a.Status == ApplicationStatus.Applied || a.Status == ApplicationStatus.Interviewed));

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfStudentsNotAcceptedAnywhere", "after");

            return count;
        }

        /// <summary>
        /// This method implements the 'NumberOfStudentsOfferedAdmissionByAHospital' operation.
        /// </summary>
        /// <param name="hospital">The hospital.</param>
        /// <returns>The number of students offered admission by this hospital.</returns>
        public int NumberOfStudentsOfferedAdmissionByAHospital(Hospital hospital)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfStudentsOfferedAdmissionByAHospital", "before");

            if (!hospitals.Contains(hospital))
                throw new PreconditionException("System", "NumberOfStudentsOfferedAdmissionByAHospital", "The selected hospital is not present in the system");

            int count = 0;

            HashSet<Student> selectedDomain = new HashSet<Student>(hospital.Selected.Keys);
            HashSet<Student> acceptedDomain = new HashSet<Student>(hospital.OfferAccepted.Keys);
            HashSet<Student> declinedDomain = new HashSet<Student>(hospital.Declined.Keys);

            count = students.Count(s => selectedDomain.Contains(s) || acceptedDomain.Contains(s) || declinedDomain.Contains(s));

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfStudentsOfferedAdmissionByAHospital", "after");

            return count;
        }

        /// <summary>
        /// This method implements the 'NumberOfPositionsFilledInAHospital' operation.
        /// </summary>
        /// <param name="hospital">The hospital.</param>
        /// <returns>The number of positions filed in this hospital.</returns>
        public int NumberOfPositionsFilledInAHospital(Hospital hospital)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfPositionsFilledInAHospital", "before");

            if (!hospitals.Contains(hospital))
                throw new PreconditionException("System", "NumberOfPositionsFilledInAHospital", "The selected hospital is not present in the system");

            int count = 0;

            HashSet<Student> acceptedDomain = new HashSet<Student>(hospital.OfferAccepted.Keys);

            count = students.Count(s => acceptedDomain.Contains(s)); 

            return count;
        }

        /// <summary>
        /// This method implements part of the 'StudentStatistics' operation.
        /// </summary>
        /// <param name="whichStudent">The student.</param>
        /// <returns>The number of positions this student applied for.</returns>
        public int NumberOfPositionsStudentAppliedTo(Student whichStudent)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfPositionsStudentAppliedTo", "before");

            int count = 0;

            count = whichStudent.Applications.Count;

            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfPositionsStudentAppliedTo", "after");

            return count;
        }

        /// <summary>
        /// This method implements part of the 'StudentStatistics' operation.
        /// </summary>
        /// <param name="whichStudent">The student.</param>
        /// <returns>The number of interviews this student attended.</returns>
        public int NumberOfInterviewsStudentAttended(Student whichStudent)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfInterviewsStudentAttended", "before");


            int count = 0;

            count = whichStudent.Applications.Count(a => a.Status != ApplicationStatus.Applied);

            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfInterviewsStudentAttended", "after");

            return count;
        }

        /// <summary>
        /// This method implements part of the 'StudentStatistics' operation.
        /// </summary>
        /// <param name="whichStudent">The student.</param>
        /// <returns>The number of admissions this student recieved.</returns>
        public int NumberOfAdmissionsStudentRecieved(Student whichStudent)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfAdmissionsStudentRecieved", "before");

            int count = 0;

            count = whichStudent.Applications.Count(a => a.Status != ApplicationStatus.Applied && a.Status != ApplicationStatus.Interviewed);

            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("System", "NumberOfAdmissionsStudentRecieved", "after");

            return count;
        }

        public ObservableHashSet<Student> Students
        {
            get
            {
                return this.students;
            }
        }

        public ObservableHashSet<Hospital> Hospitals
        {
            get
            {
                return this.hospitals;
            }
        }
    }
}
